package com.ffe.flower.engine.component;

import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.util.Assert;

import com.ffe.flower.engine.dto.FlowDefinition;
import com.ffe.flower.engine.extend.IProcessLogger;
import com.ffe.flower.engine.util.ApplicationContextHelper;

public final class ProcessEngine {

  private static final Map<String, FlowDefinition> FLOW_DEFINITIONS = new HashMap<>();

  private static final Map<String, IProcessLogger> PROCESS_LOGGERS = new HashMap<>();

  public static void validate() {
    for (FlowDefinition flowDefinition : FLOW_DEFINITIONS.values()) {
      depth(flowDefinition);
      Set<Enum<?>> tmp = new HashSet<>();
      duplicate(tmp, flowDefinition);
      List<Enum<?>> list = new LinkedList<>();
      finished(list, flowDefinition);
      Assert.isTrue(list.size() == 1, "Finished FlowState number must be only one!");
    }
  }

  private static void depth(FlowDefinition flowDefinition) {
    Assert.isTrue(flowDefinition.getFlowState() != null && !flowDefinition.getNexts().isEmpty(),
        "FlowState number must be at least 2");
  }

  private static void duplicate(Set<Enum<?>> tmp, FlowDefinition flowDefinition) {
    if (!tmp.contains(flowDefinition.getFlowState())) {
      tmp.add(flowDefinition.getFlowState());
    } else {
      Assert.isTrue(false, "FlowState is duplicated");
    }
    for (FlowDefinition fd : flowDefinition.getNexts()) {
      duplicate(tmp, fd);
    }
  }

  private static void finished(List<Enum<?>> tmp, FlowDefinition flowDefinition) {
    if (flowDefinition.isFinished()) {
      tmp.add(flowDefinition.getFlowState());
      Assert.isTrue(tmp.size() <= 1, "Finished FlowState number must be only one!");
    }
    for (FlowDefinition fd : flowDefinition.getNexts()) {
      finished(tmp, fd);
    }
  }

  public static FlowDefinition createProcess(String processId, Enum<?> startState) {
    FlowDefinition flowDefinition = new FlowDefinition(processId, startState);
    Assert.isTrue(!FLOW_DEFINITIONS.containsKey(processId), "duplicated processId");
    FLOW_DEFINITIONS.put(processId, flowDefinition);
    return flowDefinition;
  }

  public static FlowDefinition createDefinition(Enum<?> flowState, boolean finished) {
    return new FlowDefinition(flowState, finished);
  }

  public static FlowDefinition createDefinition(Enum<?> flowState) {
    return new FlowDefinition(flowState);
  }

  public static FlowDefinition findProcess(String processId) {
    return FLOW_DEFINITIONS.get(processId);
  }

  public static FlowDefinition findProcessState(String processId, Enum<?> state) {
    FlowDefinition flowDefinition = findProcess(processId);
    if (flowDefinition != null) {
      return findFlowDefinition(flowDefinition, state);
    }
    return null;
  }

  private static FlowDefinition findFlowDefinition(FlowDefinition flowDefinition, Enum<?> state) {
    if (state == flowDefinition.getFlowState()) {
      return flowDefinition;
    } else {
      if (flowDefinition.hasNext()) {
        for (FlowDefinition definition : flowDefinition.getNexts()) {
          return findFlowDefinition(definition, state);
        }
      }
    }
    return null;
  }

  public static void createLogger(String processId, String loggerName) {
    PROCESS_LOGGERS.put(processId, ApplicationContextHelper.getBean(loggerName, IProcessLogger.class));
  }

  public static IProcessLogger findLogger(String processId) {
    return PROCESS_LOGGERS.get(processId);
  }

}
